python-cgi + ajax 实现异步响应表单

简单的界面用于个人 or 公司内部 demo。

cgi

CGI 目前由NCSA维护,NCSA定义CGI如下:
CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口。

架构

教程

cgi 实在是…… 太!简!单!太!方!便了!不废话,上个教程。
Python CGI编程

配置

apache cgi 配置教程网上都有,上面的教程也有介绍,值得一提的是,我遵照了若干教程,然而并没能用 mac 自带的 apache 配置 cgi 成功,最后是用 xampp 配置好的,推测是原有的 xampp 对本机的 apache 有改动。

下面记录下 xampp 配置 cgi 过程。
在 /Application/XAMPP/etc/httpd.conf 改

1
2
3
4
5
6
<Directory "/Applications/XAMPP/xamppfiles/cgi-bin/">
AllowOverride None
Options Indexes FollowSymLinks MultiViews ExecCGI
Order allow,deny
Allow from all
</Directory>

在 /Applications/XAMPP/xamppfiles/apache2/conf/httpd.conf 改

1
2
3
4
5
6
<Directory "/Applications/XAMPP/xamppfiles/apache2/htdocs">
Options Indexes FollowSymLinks MultiViews ExecCGI
AllowOverride All
Order allow,deny
Allow from all
</Directory>

重启,把 cgi 代码放到 /Applications/XAMPP/xamppfiles/cgi-bin/ 目录下运行,如果无法运行,改权限

chmod 755 test.py

附测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python
# -*- coding: UTF-8 -*-
print "Content-type:text/html"
print # 空行,告诉服务器结束头部
print '<html>'
print '<head>'
print '<meta charset="utf-8">'
print '<title>Hello Word - 我的第一个 CGI 程序!</title>'
print '</head>'
print '<body>'
print '<h2>Hello Word! 我是来自菜鸟教程的第一CGI程序</h2>'
print '</body>'
print '</html>'

浏览器打开 http://localhost/cgi-bin/test.py, 测试成功。

cgi 调试

为了浏览器调试,在 cgi 文件中加上下面的代码。

1
2
import cgitb
cgitb.enable()

这时,debug信息会显示在浏览器上面,方便调试。生产环境中出于安全的考虑,一般会关掉debug的功能。

ajax

简介

AJAX 全称 Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。它并非一种新的技术,而是以下几种原有技术的结合体。

  1. 使用 CSS 和 XHTML 来表示。
  2. 使用 DOM 模型来交互和动态显示。
  3. 使用 XMLHttpRequest 来和服务器进行异步通信。
  4. 使用 javascript 来绑定和调用。

通过 AJAX 异步技术,可以在客户端脚本与 web 服务器交互数据的过程中使用 XMLHttpRequest 对象来完成 HTTP 请求(Request)/应答(Response)模型:

  1. 不需要用户等待服务端响应。在异步派发 XMLHttpRequest 请求后控制权马上就被返回到浏览器。界面不会出现白板,在得到服务器响应之前还可以友好的给出一个加载提示。
  2. 不需要重新加载整个页面。为 XMLHttpRequest 注册一个回调函数,待服务器响应到达时,触发回调函数,并且传递所需的少量数据。“按需取数据”也降低了服务器的压力。
  3. 不需要使用隐藏或内嵌的框架。在 XHR 对象之前,模拟 Ajax 通信通常使用 hack 手段,如使用隐藏的或内嵌的框架(iframe标签)。

重要对象:XMLHttpRequest

XMLHttpRequest 是一套可以在 Javascript、VbScript、Jscript 等脚本语言中通过http协议传送或接收 XML 及其他数据的一套API。

主要函数(客户端)

  • open(method,url,async, bstrUser, bstrPassword)
    规定请求的类型、URL 以及是否异步处理请求。

  • setRequestHeader(name,value)
    自定义HTTP头部信息。需在open()方法之后和send()之前调用,才能成功发送请求头部信息。
    onreadystatchange,

  • send(string)
    将请求发送到服务器。参数string仅用于POST请求;对于GET请求的参数写在url后面,所以string参数传递null。

简单实例

封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var myAjax = {
// XMLHttpRequest IE7+, Firefox, Chrome, Opera, Safari ; ActiveXObject IE6, IE5
xhr: window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
get: function (url, callback) {
this.xhr.open('get', url);
this.onreadystatechange(callback, this.xhr);
this.xhr.send(null);
},
post: function (url, data, callback) {
this.xhr.open('post', url);
this.xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
this.onreadystatechange(callback, this.xhr);
this.xhr.send(data);
},
onreadystatechange: function (func, _xhr) {
_xhr.onreadystatechange = function () {
if (_xhr.readyState == 4) {
if (_xhr.status == 200) {
func(_xhr.responseText);
}
}
}
}
}

使用

1
2
3
4
5
6
7
8
9
$('#btn_nowTime1').bind('click', null
, function () {
myAjax.post('AjaxHandler.ashx', 'func=GetServerTime'
, function (data) {
if (data)
alert(data);
}
);
});

socket

Socket 是任何一种计算机网络通讯中最基础的内容。例如当你在浏览器地址栏中输入 www.oschina.net 时,你会打开一个套接字,然后连接到 www.oschina.net 并读取响应的页面然后然后显示出来。而其他一些聊天客户端如 gtalk 和 skype 也是类似。任何网络通讯都是通过 Socket 来完成的。
下面简单介绍下 python 的 socket 编程。

server

主要函数

  • s.bind(address)
    将 socket 绑定到地址, 在 AF_INET 下,以元组(host,port)的形式表示地址.

  • s.listen(backlog)
    开始监听 TCP 传入连接。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。

  • s.accept()
    接受TCP连接并返回(conn,address),其中 conn 是新的 socket 对象,可以用来接收和发送数据。address是连接客户端的地址。

过程

  1. 创建 socket

    socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
  2. 绑定到本地IP与端口

    s.bind()
  3. 开始监听连接

    s.listen()
  4. 进入循环,不断接受客户端的连接请求

    s.accept()
  5. 然后接收传来的数据,并发送给对方数据

    # 接收数据
    s.recv()
    # 发送数据
    s.sendall()
  6. 传输完毕后,关闭socket

    s.close()

client

主要函数

  • s.connect(address)
    连接到 address 处的 socket。一般 address 的格式为元组(hostname,port),如果连接出错,返回 socket.error 错误。

  • s.connect_ex(adddress)
    功能与 connect(address) 相同,但是成功返回0,失败返回errno的值。

过程

  1. 创建 socket,连接远端地址

    socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
    s.connect()
  2. 连接后发送数据和接收数据

    s.sendall()
    s.recv()
  3. 传输完毕后,关闭 socket

    s.close()

server, client 主要公共函数

  • s.recv(bufsize[,flag])
    接受 TCP socket 的数据。数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

  • s.send(string[,flag])
    发送TCP数据。将string中的数据发送到连接的 socket 。返回值是要发送的字节数量,该数量可能小于 string 的字节大小。

  • s.sendall(string[,flag])
    完整发送TCP数据。将string中的数据发送到连接的 socket ,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。

  • s.close()
    关闭 socket。

实例

实现一个简单的 socket,用户输入问题,刷新页面响应。

server 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import socket
import sys
HOST = 'localhost' # Symbolic name meaning all available interfaces
PORT = 9000 # Arbitrary non-privileged port
# create tcp socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
sys.exit()
print 'Socket bind complete'
s.listen(10)
print 'Socket now listening'
while 1:
#wait to accept a connection - blocking call
conn, addr = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
data = conn.recv(1024)
reply = '{"search":{"cost":0.01,"data":[{"ask":"在哪里","answer":["不是在这里","你在这里","好久不见"],"score":99}]},"model":{"cost":0.02,"answer":"在这里哈哈哈"}}'
print reply
if not data:
break
conn.sendall(reply)
conn.close()
s.close()

client 完整代码

client 响应表单

mysocket.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import json
import socket
import cgitb
import cgi
cgitb.enable()
form = cgi.FieldStorage()
question = form.getvalue('question')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 9000))
message = question
try:
# Set the whole string
sock.sendall(message)
except socket.error:
# Send failed
print 'Send failed'
sys.exit()
# sock.send(data)
result = json.loads(sock.recv(10240))
# 以下代码处理 json 并显示表单
search_cost = result['search']['cost']
answers = result['search']['data'][0]['answer']
ans = answers[0]
search_score = result['search']['data'][0]['score']
rnnAns = result['model']
print "Content-type:text/html"
print
print '<html>'
print '<head>'
print '<meta charset="utf-8">'
print '<title>TEST</title>'
print '</head>'
print '<body align="center"><div width="980" align="center">'
print "<h2>Question: " + question + "</h2>"
print "<hr ><h2> Top 3</h2>"
print "Cost: "
print search_cost
print "&nbsp;&nbsp;&nbsp;&nbsp;"
print "Score: "
print search_score
print "<br >"
for i in range(1, len(answers) + 1):
print "<p>"
print i
print ":&nbsp;"
print answers[i - 1].encode('utf8')
print "<br>"
print "<hr /><h2> RNN &nbsp; Model </h2>"
print "Cost: "
print rnnAns['cost']
print "<br><p>"
print rnnAns['answer'].encode('utf8')
print "</p>"
print "&nbsp;&nbsp;&nbsp;&nbsp;"
print '</div></body>'
print '</html>'

client 主页面

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<style>
body {
font-family: "arial";
}
table,
td,
th {
border: 1px solid #ddd;
}
th,
td {
border-bottom: 1px solid #ddd;
height: 50px;
text-align: left;
}
table {
border-collapse: collapse;
width: 80%;
}
}
</style>
<title>Simple Evaluation Check</title>
<script language="Javascript">
function xmlhttpPost(strURL) {
var xmlHttpReq = false;
var self = this;
// Mozilla/Safari
if (window.XMLHttpRequest) {
self.xmlHttpReq = new XMLHttpRequest();
}
// IE
else if (window.ActiveXObject) {
self.xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP");
}
self.xmlHttpReq.open('POST', strURL, true);
# 设置 content-type
self.xmlHttpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
self.xmlHttpReq.onreadystatechange = function() {
if (self.xmlHttpReq.readyState == 4) {
updatepage(self.xmlHttpReq.responseText);
}
}
self.xmlHttpReq.send(getquerystring());
}
function getquerystring() {
var form = document.forms['f1'];
var question = form.question.value;
qstr = 'question=' + question; // NOTE: no '?' before querystring
return qstr;
}
function updatepage(str) {
document.getElementById("result").innerHTML = str;
}
</script>
</head>
<body>
<form name="f1">
<p>Question:
<input name="question" type="text">
<input value="Go" type="button" onclick='JavaScript:xmlhttpPost("cgi-bin/mysocket.py")'>
</p>
<div id="result"></div>
</form>
</body>
</html>

运行

运行 server.py,浏览器输入 http://localhost/index.html 查看效果
主页

异步响应

server

参考链接
Python CGI编程
Writing Your First Python CGI – Ajax Script
触碰jQuery:AJAX异步详解

徐阿衡 wechat
欢迎关注:徐阿衡的微信公众号
客官,打个赏呗~